package org.jrfe.gfx;

import org.jrfe.JRFE;
import org.jrfe.gfx.g2d.shapes.Shape;
import org.jrfe.gfx.g2d.text.Text;
import org.jrfe.gfx.g2d.text.TextAlignment;
import org.jrfe.gfx.g2d.text.Font.CharData;
import org.jrfe.math.Vector2f;
import org.lwjgl.opengl.GL11;

/**
 * JRFE rendering all rendering is done throw this class </br> Rendering must
 * start with JRFERenderer.beginScene() </br> And must end with
 * JRFERenderer.endScene(); </br>
 * 
 * @author Kalvis Freimanis
 * 
 */
public abstract class JRFERenderer {

	/**
	 * Currently binded texture
	 */
	protected static int activeTex = 0;
	
	protected static boolean blending = false;

	/**
	 * Currently active blending
	 */
	protected static BlendFunc activeBlend = new BlendFunc(
			JRFE.Blending.Src.ONE, JRFE.Blending.Dest.ZERO);

	/**
	 * Signals JRFE that rendering will being Must be called each frame
	 * beginning
	 */
	public static void beginScene() {
		GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
		GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);

		if (activeTex != 0) {
			GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
			GL11.glEnable(GL11.GL_TEXTURE_2D);
		}

	}

	/**
	 * Signals JRFE that rendering has ended Must be called at each frames end
	 */
	public static void endScene() {
		GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
		GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
		
		if(activeTex != 0){
			GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
			GL11.glDisable(GL11.GL_TEXTURE_2D);
		}
	}

	/**
	 * Set blending state
	 * 
	 * @param blendFunc
	 *            {@link BlendFunc} how to blend
	 */
	public static void applyBlending(BlendFunc blendFunc) {
		
		if(blendFunc == null){
			if(blending){
				GL11.glDisable(GL11.GL_BLEND);
				blending = false;
			}
		}
		else if (blendFunc.src != activeBlend.src
				|| blendFunc.dest != activeBlend.dest) {
			
			if(!blending){
				GL11.glEnable(GL11.GL_BLEND);
				blending = true;
			}
			
			// Apply blending only if it differs form current one
			// this is done to minimize calls to opengl code
			
			activeBlend.dest = blendFunc.dest;
			activeBlend.src = blendFunc.src;
			GL11.glBlendFunc(blendFunc.src, blendFunc.dest);
		}
	}

	/**
	 * Set line width
	 * 
	 * @param width
	 */
	public static void setLineWidth(float width) {
		GL11.glLineWidth(width);
	}

	/**
	 * Render text
	 * 
	 * @param text
	 *            {@link Text} text Object
	 */
	public static void renderText(Text text) {
		renderText(text, TextAlignment.LEFT);
	}

	/**
	 * Render test
	 * @param text Test object to render
	 * @param alignment text alignment {@link TextAlignment}
	 */
	public static void renderText(Text text, TextAlignment alignment) {

		// Check if wee need to enable texture state
		if (activeTex == 0) {
			// Texture state is disable so Enable texture state
			// (Enable GL_TEXTURE_COORD_ARRAY so that batch rendering works as
			// expected )
			GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
			GL11.glEnable(GL11.GL_TEXTURE_2D);

			// Bind font texture
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, text.getFont().getTexture()
					.getTextureID());
			
			activeTex = text.getFont().getTexture().getTextureID();
		
		} else {
			// Texture state is enabled

			// Check is currently binded texture is this text texture
			// if not bind this text texture
			if (text.getFont().getTexture().getTextureID() != activeTex) {
				
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, text.getFont()
						.getTexture().getTextureID());

				// Set active texture value to text texture
				activeTex = text.getFont().getTexture().getTextureID();
			}
		}

		// set text color
		GL11.glColor4f(text.getColor().r, text.getColor().g, text.getColor().b,
				text.getColor().a);

		// push current matrix so that we can apply all needed transformation
		// and not worry about other shapes and texts
		pushMatrix();

		// translate matrix to where text needs to be rendered
		translate(text.getLoc().x, text.getLoc().y);

		switch (alignment) {
		case LEFT:
			
			break;
		case CENTERED:
			translate(-(text.getTextWidth()/2), 0);
			break;
		case RIGHT:
			translate(-(text.getTextWidth()), 0);			
			break;
		}

		// translate matrix for text size
		scale(text.getScale(), text.getScale());

		// See what king of text type we got Dynamic of Static
		switch (text.getType()) {
		case DYNAMIC:

			// Obtain text string as char[]
			char[] cs = text.getText().toCharArray();

			// loop throw chars and render them
			CharData curent;
			for (int i = 0; i < cs.length; i++) {
				// find char data
				curent = text.getFont().getCharArray()[cs[i]];
				// translate matrix for spacing between chars
				translate(curent.xExtend, 0);
				// render character
				GL11.glCallList(curent.listId);
				// translate matrix for spacing between chars
				translate(curent.xExtend, 0);
			}

			// pop matrix back to old one

			break;
		case STATIC:

			// render text (Static text is one display list)
			GL11.glCallList(text.getListId());
			
			break;
		default:
			break;
		}

		// pop matrix back to old one
		popMatrix();

	}

	/**
	 * Render vertex Batch
	 * 
	 * @param vset
	 *            {@link VertexBatch} to render
	 */
	public static void renderVertexBatch(VertexBatch vset) {

		// Bind Vertextes and Color data
		GL11.glVertexPointer(2, 0, vset.getVertexs());
		GL11.glColorPointer(4, 0, vset.getColors());

		// See if Vertex Batch is using texture
		if (vset.getTexture() != 0) {
			// VertexBatch is using texture

			// Cheak if cullrently binded texute is not VertexBatch texture
			if (activeTex != vset.getTexture()) {
				// Need to bind VertexBatch texture

				// if last texture was no texture enable texture states
				if (activeTex == 0) {
					// System.out.println("Test");
					GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
					GL11.glEnable(GL11.GL_TEXTURE_2D);
				}

				// Bind Vertex Batch texture
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, vset.getTexture());

				// Set activeTexture to VertexBatch texture
				activeTex = vset.getTexture();
			}

			// Bind vertex batch texture cords
			GL11.glTexCoordPointer(2, 0, vset.getTexCords());
		} else {
			// VertexBatch has no texture
			// Check if current activeTexture is texture not 0
			if (activeTex != 0) {
				// Current active texture is texture disable texture states and
				// unbind currently active texture
				GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
				GL11.glDisable(GL11.GL_TEXTURE_2D);
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
				activeTex = 0;
			}
		}

		// Check what Type is VertexBatch
		switch (vset.getBatchType()) {
		case SIMPLE:

			// TODO See what can do about bottleneck
			// Render VertexBatch
			GL11.glDrawArrays(vset.getPtype(), 0, vset.getElements());

			break;
		case INDEXED:
			// Render IdxVertexBatch
			IdxVertexBatch ivb = (IdxVertexBatch) vset;
			GL11.glDrawElements(vset.getPtype(), ivb.getIndexes());
			break;
		}

	}

	/**
	 * Render shape
	 * 
	 * @param shape
	 *            {@link Shape} to render
	 */
	public static void renderShape(Shape shape) {
		// Apply shapes blending
		applyBlending(shape.getBlending());

		// push matrix so that shapes transformations are in apart matrix
		// and don't interferer with other matrixes
		pushMatrix();

		Vector2f v = shape.getLocation();

		// Check if needs to translate matrix
		if (v.x != 0 || v.y != 0) {
			translate(v.x, v.y);
		}

		v = shape.getScale();
		// Check is needs to scale matrix
		if (v.x > 0 || v.y > 0) {
			scale(v.x, v.y);
		}

		// Check if need to rotate matrix
		if (shape.getRotation() != 0) {
			rotate(shape.getRotation());
		}

		// renders shapes vertex batch
		renderVertexBatch(shape.getBatch());

		// pop matrix back old one
		popMatrix();
	}

	/**
	 * Push upper Matrix see OpenGL pushMatrix()
	 */
	public static void pushMatrix() {
		GL11.glPushMatrix();
	}

	/**
	 * Translate Matrix see OpenGL glTranslate
	 * 
	 * @param x
	 *            coordinate
	 * @param y
	 *            coordinate
	 */
	public static void translate(float x, float y) {
		GL11.glTranslatef(x, y, 0);
	}

	/**
	 * Rotate Matrix see OpenGl glRotate
	 * 
	 * @param r
	 */
	public static void rotate(float r) {
		GL11.glRotatef(r, 0, 0, 1);
	}

	/**
	 * Scale Matrix see OpenGl glScale
	 * 
	 * @param x
	 * @param y
	 */
	public static void scale(float x, float y) {
		GL11.glScalef(x, y, 1);
	}

	/**
	 * Pop Matrix see OpenGl glPopMatrix
	 */
	public static void popMatrix() {
		GL11.glPopMatrix();
	}

}
